// Bluetooth data communication channel
import dxMap from './dxMap.js'
import common from './dxCommon.js'
import std from './dxStd.js'
import bus from './dxEventBus.js'
import uart from './dxUart.js'
import gpio from './dxGpio.js'
import * as os from "os"
import * as qStd from "std"
import log from './dxLogger.js'
const map = dxMap.get('default')
const ble = {}


ble.VG = {
	RECEIVE_MSG: '__ble__MsgReceive',
}

/**
 * Simplify the use of the Bluetooth Low Energy communication protocol:

 * 1. Receive Data: After receiving the binary data from the TLV, parse it into an object and send it out as an event on the eventbus (uart.VG.RECEIVE_MSG+options.id)

 * Returned object format: {cmd:"2a",result:"01",length:7,data:"0a1acc320fee32",bcc:true}

 * cmd: A 1-byte command word, a hexadecimal string

 * result: A 1-byte identifier word indicating the result of data processing, success, failure, or other status. Only feedback data has an identifier, a hexadecimal string.

 * length: The length of the data, defined as 2 bytes in TLV, here directly converted to a decimal number.

 * data: A multi-byte data field, a hexadecimal string.

 * bcc: BCC checksum success or failure.

 * 2. Sending Data: Convert the object into TLV format binary data before sending it. This can be done using `uart.sendVg('data to be sent', id)`. The data format is as follows:

 * There are two data formats for sending:
    1. Object format: `{cmd:"2a",result:"01",length:7,data:"0a1acc320fee32"}`
    2. Complete hexadecimal string: `'55AA09000000F6'`

 * 3. With the same id, multiple calls to `runvg` will only execute once.
 * 
 * @param {object} options Startup parameters
 *			@param {number} options.type Channel type, refer to the enumeration TYPE, required (compatible with USBHID block transfer, default 1024 blocks).
 *			@param {string} options.path Different devices or different types of channels on the same device may correspond to different paths. For example, the value corresponding to path 485 on DW200 is "/dev/ttyS2", which is required.
 *			@param {number} options.result 0 and 1 (default is 0) indicate whether the received data or the transmitted data contains the identifier byte. 0 means the received data does not include the identifier byte, and the transmitted data does; 1 means the opposite.
 *			@param {number} options.passThrough If passThrough is set to true, the received data will use the pass-through mode. This field is not required.
 *          @param {string} options.id  Handle ID, optional (a unique ID is required if initializing multiple instances).
 * 			@param {number} options.bleName Bluetooth name to set (optional)
 *          @param {string} options.broadcast The broadcast identifier to be set is optional.
 */
ble.run = function (options) {
	if (options === undefined || options.length === 0) {
		throw new Error("dxuart.runvg:'options' parameter should not be null or empty")
	}
	if (options.id === undefined || options.id === null || typeof options.id !== 'string') {
        // handle id
        options.id = ""
    }
	if (options.type === undefined || options.type === null) {
		throw new Error("dxuart.runvg:'type' should not be null or empty")
	}
	if (options.path === undefined || options.path === null) {
		throw new Error("dxuart.runvg:'path' should not be null or empty")
	}
	let oldfilepre = '/app/code/dxmodules/vgBleWorker'
	let content = std.loadFile(oldfilepre + '.js').replace("{{id}}", options.id)
	let newfile = oldfilepre + options.id + '.js'
	std.saveFile(newfile, content)
	let init = map.get("__vgble__run_init" + options.id)
	if (!init) {//Ensure initialization only once
		map.put("__vgble__run_init" + options.id, options)
		bus.newWorker(options.id || "__ble",newfile)
	}
}

/**
 * Bluetooth reset (ensure GPIO and Bluetooth are initialized before resetting).
 */
ble.reset = function () {
    gpio.setFuncGpio(75, 0x04)
    os.sleep(10)
    gpio.setFuncGpio(75, 0x05)
    os.sleep(10)
}

/**
 * Send Bluetooth data
 * @param {*} data For example: 55AA31010001CE or 55 AA 31 01 00 01 CE
 * @returns 
 */
ble.send = function(data, id) {
    data = data.replaceAll("\"", "")
    uart.send(common.hexStringToArrayBuffer(data), id)
}

/**
 * Query Bluetooth configuration information
 * @param {*} 
 * @returns 
 */
ble.getBleConfig = function(id) {
    let pack = { "head": "55aa", "cmd": "60", "result": "00", "dlen": 6, "data": [0x7e, 0x01, 0x00, 0x02, 0x00, 0xfe] }
    pack.crc = ble.genCrc(pack)
    let data = pack.head + pack.cmd + pack.result + common.decimalToLittleEndianHex(pack.dlen, 2) + pack.data.map(v => v.toString(16).padStart(2, '0')).join("") + pack.crc.toString(16).padStart(2, '0')
    ble.send(data, id)
    for (let index = 0; index < 20; index++) {
        let res = map.get("__ble__config")
        if(res){
            return res
        }
        os.sleep(50)
    }
    return null
}

/**
 * Configure Bluetooth settings
 * @param {*} param param.name: Bluetooth name; param.mac: Bluetooth MAC; param.broadcastSn: Tlv Bluetooth broadcast SN; param.broadcastStatus: Tlv Bluetooth broadcast status flag.
 * @returns 
 */
ble.setBleConfig = function(param, id) {
    if (!param || !param.name) {
        return
    }
    let nameTlv = ""
    let macTlv = ""
    let broadcastSnTlv = ""
    let broadcastStatusTlv = ""

    let name = param.name
    if (name !== undefined) {
        if (name && !/[\u4E00-\u9FA5]/g.test(name) && name.length <= 10) {
            nameTlv = "10" + common.decimalToLittleEndianHex(name.length, 1) + common.stringToHex(name)
        } else {
            log.error("Bluetooth names cannot contain Chinese characters and cannot exceed 10 characters in length.");
            return
        }
    }

    // Internal method, MAC verification
    let VBAR_M_BLE_MACLEN = 6
    function bleMacIsValid(mac) {
        if (mac.length != VBAR_M_BLE_MACLEN) {
            return false;
        }
        if (!mac.some(v => v !== "00") || mac[VBAR_M_BLE_MACLEN - 1] & 0xc0 !== 0xc0) {
            return false;
        }
        return true;
    }

    let mac = param.mac
    if (mac !== undefined) {
        if (mac && mac.length === 12 && /^[a-zA-Z0-9]*$/.test(mac) && bleMacIsValid(mac.match(/.{2}/g))) {
            macTlv = "11" + "06" + mac
        } else {
            log.error("Bluetooth MAC address format error");
            return
        }
    }

    let broadcastSn = param.broadcastSn
    if (broadcastSn !== undefined) {
        if (broadcastSn && broadcastSn.length === 12 && /^[a-zA-Z0-9]*$/.test(broadcastSn)) {
            macTlv = "12" + "06" + mac
        } else {
            log.error("Bluetooth broadcast SN format error");
            return
        }
    }

    let broadcastStatus = param.broadcastStatus
    if (broadcastStatus !== undefined) {
        if (broadcastStatus) {
            macTlv = "15" + "01" + broadcastStatus
        } else {
            log.error("Bluetooth broadcast status identifier format error");
            return
        }
    }

    let content = "7a00" + nameTlv + macTlv + broadcastSnTlv + broadcastStatusTlv + "fe"

    let pack = { "head": "55aa", "cmd": "60", "result": "00", "dlen": content.length / 2, "data": content.match(/.{2}/g).map(v => parseInt(v, 16)) }
    pack.crc = ble.genCrc(pack)
    let data = pack.head + pack.cmd + pack.result + common.decimalToLittleEndianHex(pack.dlen, 2) + pack.data.map(v => v.toString(16).padStart(2, '0')).join("") + pack.crc.toString(16).padStart(2, '0')
    ble.send(data, id)
}

/**
 * Main control switch Bluetooth broadcast
 * @param {*} 
 * @returns 
 */
ble.ctrlBroadcast = function(id) {

    let pack = { "head": "55aa", "cmd": "60", "result": "00", "dlen": 7, "data": [0x7e, 0x01, 0x00, 0x03, 0x01, 0x01, 0xfe] }
    pack.crc = ble.genCrc(pack)
    let data = pack.head + pack.cmd + pack.result + common.decimalToLittleEndianHex(pack.dlen, 2) + pack.data.map(v => v.toString(16).padStart(2, '0')).join("") + pack.crc.toString(16).padStart(2, '0')
    ble.send(data, id)
    for (let index = 0; index < 20; index++) {
        let res = map.get("__ble__config")
        if(res){
            return res
        }
        os.sleep(50)
    }
    return null
}


/**
 * Scanning Device
 * Default command sent: 55aa600010000a0000010a34210000030060006000fe66
 * 55aa6000                 Protocol header
 * 1000                     Length
 * 0a0000                   P1-P3
 * 010a                     T=01 L=0a
 * 3421000003               V Scan duration(4Byte)	Scan broadcast type(1Byte)
 * 0060006000               V Scan type(1Byte)	Scan interval(2Byte)     Scan window(2Byte)
 * fe                       CONN_ID
 * 66                       Verification word
 * Default response received: 55aa6007000a0000010100fe
 * Default asynchronous reply: 0a80010000cc01d0000c059ec0020106030356470dff01af0a00313131324651007bfe
 * 0a8001                                           P1-P3
 * 00                                               Event Status 00:Scanning 01: Scan ended
 * 00                                               Broadcast type
 * cc                                               RSSI
 * 01                                               MAC Type
 * d0000c059ec0                                     MAC address
 * 020106030356470dff01af0a00313131324651007b       ADV Struct
 * fe                                               conn_id
 */
ble.scanDevice = function(id, config = {}){
    // Scan duration(4Byte)
    let scanTime = config.scanTime || 0x03e8
    // Scan broadcast type(1Byte)
    let broadcastType = config.broadcastType || 0x03
    // Scan type(1Byte)
    let scanType = config.scanType || 0x00
    // Scan interval
    let scanInterval = config.scanInterval || 0x60
    // Scan window
    let scanWindow = config.scanWindow || 0x60

    let cmd = "55aa600010000a0000010a" +
        ble.toLittleEndianHex(scanTime, 4) +
        broadcastType.toString(16).padStart(2, '0') +
        scanType.toString(16).padStart(2, '0') +
        ble.toLittleEndianHex(scanInterval, 2) +
        ble.toLittleEndianHex(scanWindow, 2) + 
        "fe";
    
    cmd = cmd + ble.genStrCrc(cmd).toString(16).padStart(2, '0')

    print("scanDevice sends scanning device commands: ", cmd)
    ble.send(cmd, id)
}

/**
 * Stop scanning equipment
 * Default command sent: 55aa600006000a00000200fe6f
 * 55aa6000     Protocol header
 * 0600         Length
 * 0a0000       P1-P3
 * 0200         T=02 L=00 V=null
 * fe           CONN_ID
 * 6f           CRC checksum
 * Default response received: 55aa6007000a0000020100fe6e
 */
ble.stopScanning = function(id){
    let cmd = "55aa600006000a00000200fe6f"
    print("stopScanning sends a command to stop scanning the device.: ", cmd)
    ble.send(cmd, id)
}

/**
 * Request to connect device
 * Default command sent: 55aa600015000a0000030f01d0000c059ec018001a0000002800fede
 * 55aa6000                             Protocol header
 * 1500                                 Length
 * 0a0000                               P1-P3
 * 030f                                 T=03 L=0f
 * 01                                   V: MAC Type
 * d0000c059ec0                         V: MAC address
 * 1800                                 V: interval_min
 * 1a00                                 V: interval_max
 * 0000                                 V: latency
 * 2800                                 V: timeout
 * fe                                   CONN_ID
 * 0a                                   CRC checksum
 * Default response received: 55aa6007000a0000030100fe
 * 
 * Default asynchronous reply: 55aa600b000a800202d0000c059ec005
 * 55aa60           Protocol header
 * 0b00             Length
 * 0a8002           P1-P3
 * 02               Event Status
 * d0000c059ec0     MAC address
 * 05               CONN_ID(The returned actual connection ID, which defaults to 0xFE if no connection has been established.)
 */
ble.connect = function(id, config = {}){

    let macType = config.macType || 0x01
    let macAddr = config.macAddr || "c09e050c00d0"
    let intervalMin = config.intervalMin || 0x18
    let intervalMax = config.intervalMax || 0x1a
    let latency = config.latency || 0x00
    let timeout = config.timeout || 0x28
    let conTimeout = config.conTimeout || 0x01f4

    let cmd = "55aa600015000a0000030f" +
        macType.toString(16).padStart(2, '0') +
        ble.toLittleEndianString(macAddr) +
        ble.toLittleEndianHex(intervalMin, 2) + 
        ble.toLittleEndianHex(intervalMax, 2) +
        ble.toLittleEndianHex(latency, 2) + 
        ble.toLittleEndianHex(timeout, 2) +
        ble.toLittleEndianHex(conTimeout, 2) +
        "fe";
    
    let crc = ble.genStrCrc(cmd)
    cmd = cmd + crc.toString(16).padStart(2, '0')

    print("connect sends a request to connect to the device: ", cmd)
    ble.send(cmd, id)
}

/**
 * Disconnect device
 * Default command sent: 55aa600006000a000004000295 (Disconnect the device with connection number 2.)
 * 55aa6000         Protocol header
 * 0600             Length
 * 0a0000           P1-P3
 * 0400             T=04 L=00
 * 02               CONN_ID
 * 95               CRC checksum
 * Default response received: 55aa6007000a000004010009
 */
ble.disConnect = function(id, connect_id){
    let cmd = "55aa600006000a00000400" + connect_id.toString(16).padStart(2, '0');
    cmd = cmd + ble.genStrCrc(cmd).toString(16).padStart(2, '0')
    print("disConnect: ", cmd)
    ble.send(cmd, id)
}

/**
 * Scan specific services
 * UUID: 00010000-e985-b7e8-b186-e5a49ae5bca6 The Bluetooth UUID of the DW200 device is fixed.
 * Default command sent: 55aa600018000a000005120010a6bce59aa4e586b1e8b785e9000001000ca7 (Device with connection number 0c)
 * 55aa6000                             Protocol header
 * 1800                                 Length
 * 0a0000                               P1-P3
 * 0512                                 T=05 L=12
 * 00                                   FLAG
 * 10                                   UUID_LEN
 * a6bce59aa4e586b1e8b785e900000100     UUID
 * 0c                                   CONN_ID
 * a7                                   CRC checksum
 * Default response received: 55aa6007000a00000501000c
 * Default asynchronous reply: This asynchronous response has multiple versions; please see the method call section for details.
 */
ble.scanService = function(id, connect_id, config = {}){
    let flag = config.flag || 0x00
    let uuid = config.UUID || "00010000e985b7e8b186e5a49ae5bca6"
    if(!connect_id) throw new Error("connect_id Cannot be empty!");
    if(!config.uuidLen) throw new Error("uuidLen Cannot be empty!");
    let uuidLen =  config.uuidLen
    let tlvLen = uuidLen + 2
    let cmdLen = uuidLen + 8

    let cmd = "55aa6000" +
        ble.toLittleEndianHex(cmdLen, 2) + 
        "0a0000" + 
        "05" + 
        tlvLen.toString(16).padStart(2, '0') + 
        flag.toString(16).padStart(2, '0') +
        uuidLen.toString(16).padStart(2, '0') +
        ble.toLittleEndianString(uuid) +
        connect_id.toString(16).padStart(2, '0');

    cmd = cmd + ble.genStrCrc(cmd).toString(16).padStart(2, '0')
    print("scanService sends a command to scan for specific services: ", cmd)
    ble.send(cmd, id)
}

/**
 * Subscription feature notify/Indication
 * Default command sent: 55aa60000b000a000009051200130000099a (Device with connection number 09)
 * 55aa6000                             Protocol header
 * 0b00                                 Length
 * 0a0000                               P1-P3
 * 0905                                 T=09 L=05
 * 1200                                 Features Handle
 * 1300                                 CCC handle(Generally, it is a feature handle index. + 1)
 * 00                                   FLAG
 * 09                                   CONN_ID
 * 9a                                   CRC checksum
 * Default response received: 55aa6007000a000009010009
 */
ble.subCharacter = function(id, connect_id, config = {}){
    if(!connect_id) throw new Error("connect_id Cannot be empty!");
    let characterHandle =  config.characterHandle || "1200"
    let CCCHandle =  config.CCCHandle || "1300"
    let flag =  config.flag || 0x00

    let cmd = "55aa60000b000a00000905" +
        characterHandle + 
        CCCHandle + 
        flag.toString(16).padStart(2, '0') +
        connect_id.toString(16).padStart(2, '0');

    cmd = cmd + ble.genStrCrc(cmd).toString(16).padStart(2, '0')
    print("subCharacter sends the subscribed feature command: ", cmd)
    ble.send(cmd, id)
}

/**
 * Feature writing
 * Default command sent: 55aa600010000a0000080a15000155aa07010010e9fe6d
 * 55aa6000                             Protocol header
 * 1000                                 Length
 * 0a0000                               P1-P3
 * 080a                                 T=09 L=0a
 * 1500                                 Features Handle
 * 01                                   FLAG
 * 55aa07010010e9                       userData
 * fe                                   conn_id
 * 6d                                   CRC checksum
 * Default response received: 55aa6007000a0000080300150006
 * 55aa60       Protocol header
 * 0700         Length
 * 0a0000       P1-P3
 * 0803         T=08 L=03
 * 00           
 * 1500         Features Handle
 * 06           conn_id
 */
ble.write = function(id, connect_id, config){
    // write_without_response  handle 1200; flag 0x00
    // write_with_response  handle 1500; flag 0x01
    if(!connect_id) throw new Error("connect_id Cannot be empty!");
    let flag =  config.flag || 0x01
    let characterHandle =  config.characterHandle || "1500"
    let userDataLen =  config.userDataLen || 0x07
    let userData =  config.userData || "55aa07010010e9"

    let tlvLen = userDataLen + 3
    let cmdLen = userDataLen + 9

    let cmd = "55aa6000" +
        ble.toLittleEndianHex(cmdLen, 2) + 
        "0a0000" + 
        "08" + 
        tlvLen.toString(16).padStart(2, '0') + 
        characterHandle +
        flag.toString(16).padStart(2, '0') +
        userData+
        connect_id.toString(16).padStart(2, '0');

    cmd = cmd + ble.genStrCrc(cmd).toString(16).padStart(2, '0')
    print("Write characteristic write: ", cmd)
    ble.send(cmd, id)
}

/**
 * Bluetooth upgrade
 */
ble.upgrade = function(id, sourceFile, fileSha256){
    // 55aa600006000301000100fe
    // 55aa6000     Protocol header
    // 0600         Length
    // 030100       P1-P3
    // 0100         TLV data
    // fe
    let cmd01_1 = "55aa600006000301000100fe"
    let crc = ble.genStrCrc(cmd01_1)
    let cmd01_2 = "55aa600006000301000100fe" + crc.toString(16)

    print("Send upgrade command: ", cmd01_2)
    ble.send(cmd01_2, id)
    os.sleep(1000)
    ble.send(cmd01_2, id)

    let fileSize = ble.getFileSize(sourceFile);
    print("fileSize: ", fileSize)
    let littleEndianHex = ble.toLittleEndianHex(fileSize, 4)
    // 93408 
    // 182 times, remainder 224
    let chunkSize = 512
    let totality = parseInt(fileSize / 512)
    let remainder = fileSize % 512

    // 55aa6000     Protocol header
    // 2a00         Length
    // 030100       48 bytes of data
    // 0224         T=02  L=0x24
    // e06c0100     4-byte file size, little-endian length
    // bde2c44d8246a1d7ee18e7a9b2b6c3c008a076a52669c53d8c0aefab245806ad 32-byte SHA256
    // fe           1 byte
    // fb
    let cmd02_1 = "55aa6000" + "2a00" + "030100" + "0224" + littleEndianHex + fileSha256 + "fe"
    crc = ble.genStrCrc(cmd02_1)
    let cmd02_2 = cmd02_1 + crc.toString(16)

    print("Upgrade package description instructions to be sent: ", cmd02_2)
    ble.send(cmd02_2, id)


    // Open the source file (read-only mode).
    const srcFd = os.open(sourceFile, os.O_RDONLY);
    if (srcFd < 0) {
        throw new Error(`Unable to open source file: ${sourceFile}`);
    }
    // Create a buffer (e.g., 4096 bytes).
    let bufferSize = fileSize;
    let buffer = new Uint8Array(bufferSize);
    try {
        // Read data from the source file into the buffer.
        const bytesRead = os.read(srcFd, buffer.buffer, 0, bufferSize);
        if (bytesRead <= 0) {
            console.log("File copy failed.!");
        }else{
            console.log("File copied successfully!");
        }
    } finally {
        // Make sure the file descriptor is closed.
        os.close(srcFd);
    }

    // Sub-package sending of upgrade packages
    // 55aa6000     Baotou
    // 0602         Length 518
    // 030100       48 bytes of data
    // 0300         T=03  L=0x0200
    // xxxxx        512 bytes of data
    // fe           1 byte
    // fb
    os.sleep(200)
    let totalCount = 0
    for (let index = 0; index < totality + 1; index++) {
        // Calculate the start and end positions of the current sub-packet.
        let start = index * chunkSize;
        let end = Math.min(start + chunkSize, buffer.byteLength); 
        // Create an ArrayBuffer for the current packet data (key step)
        let sendBuffer = buffer.slice(start, end);
        if (index == totality) {
            // The last packet needs to be padded with the remaining bytes.
            let padding = new Uint8Array(chunkSize - remainder);
            sendBuffer = new Uint8Array([...sendBuffer, ...padding]);
            print("Last byte of data: ", sendBuffer.byteLength)
            print("Last byte of data: ", common.arrayBufferToHexString(sendBuffer))
        }

        let cmd03_1 = "55aa6000" + "0602" + "030100" + "0300" + common.arrayBufferToHexString(sendBuffer) + "fe"
        crc = ble.genStrCrc(cmd03_1)
        let cmd03_2 = cmd03_1 + crc.toString(16)
        // print("Packet information to be sent: ", cmd03_2)
        ble.send(cmd03_2, id)
        os.sleep(60)
        totalCount++
        if(totalCount == totality + 1){
            print("Upgrade package transfer complete,totalCount: ", totalCount)
        }else{
            print("The original data has been synchronized and is being transmitted in packets.,totalCount: ", totalCount)
        }
    }

    // 55aa600006000301000100fe
    // 55aa6000
    // 0600
    // 030100
    // 0400fe       TLV
    let cmd04_1 = "55aa600006000301000400fe"
    crc = ble.genStrCrc(cmd04_1)
    let cmd04_2 = "55aa600006000301000400fe" + crc.toString(16)
    print("Send upgrade end command: ", cmd04_2)
    ble.send(cmd04_2, id)
    os.sleep(50)

    // 55aa600006000301000100fe
    // 55aa6000
    // 0600
    // 030100
    // 0500fe       TLV
    let cmd05_1 = "55aa600006000301000500fe"
    crc = ble.genStrCrc(cmd05_1)
    let cmd05_2 = "55aa600006000301000500fe" + crc.toString(16)
    print("Send command to install upgrade package: ", cmd05_2)
    ble.send(cmd05_2, id)
}

ble.genCrc =  function(pack) {
    let bcc = 0;
    let dlen = pack.dlen - 1;//Remove index
    bcc ^= 0x55;
    bcc ^= 0xaa;
    bcc ^= parseInt(pack.cmd, 16);
    bcc ^= pack.result ? parseInt(pack.result, 16) : 0;
    bcc ^= (dlen & 0xff);
    bcc ^= (dlen & 0xff00) >> 8;
    for (let i = 0; i < pack.dlen; i++) {
        bcc ^= pack.data[i];
    }
    return bcc;
}

ble.toHexadecimal =  function(data) {
    return parseInt(data).toString(16).padStart(2, '0')
}

ble.getUrandom = function(len) {
    return common.systemWithRes(`dd if=/dev/urandom bs=1 count="${len}" 2>/dev/null | xxd -p`, 100).split(/\s/g)[0]
}

ble.genStrCrc = function(pack) {
    let buffer = common.hexStringToUint8Array(pack)
    print("buffer.length: " + buffer.length)
    let bcc = 0;
    for (let i = 0; i < buffer.length; i++) {
        bcc ^= buffer[i];
    }
    print("crc: ", bcc)
    return bcc;
}

ble.toLittleEndianHex = function(number, byteLength) {
    const bigNum = BigInt(number);
    
    // Parameter verification
    if (!Number.isInteger(byteLength)) throw new Error("byteLength must be an integer");
    if (byteLength < 1) throw new Error("byteLength must be greater than 0");
    if (byteLength > 64) throw new Error("Processing of bytes larger than 8 bytes is not currently supported.");

    // Numerical range check
    const bitWidth = BigInt(byteLength * 8);
    const maxValue = (1n << bitWidth) - 1n;
    if (bigNum < 0n || bigNum > maxValue) {
        throw new Error(`Value exceeds${byteLength}byte range`);
    }

    // Little-endian byte extraction
    const bytes = new Uint8Array(byteLength);
    for (let i = 0; i < byteLength; i++) {
        const shift = BigInt(i * 8);
        bytes[i] = Number((bigNum >> shift) & 0xFFn); // Make sure to use a BigInt mask.
    }

    // Format conversion
    return Array.from(bytes, b => 
        b.toString(16).padStart(2, '0')
    ).join('');
}

ble.toLittleEndianString = function(hexStr) {
    // Grouped by 1 byte (2 characters)
    let bytes = hexStr.match(/.{2}/g);
    
    // Reverse the array and concatenate them into little-endian format.
    return bytes.reverse().join('');
}

ble.getFileSize = function(filename) {
    let file = qStd.open(filename, "r");
    if (!file) {
        throw new Error("Failed to open file");
    }
    
    file.seek(0, qStd.SEEK_END);  // Move to the end of the file
    let size = file.tell();      // Get the current position (i.e., file size).
    
    file.close();
    return size;
}


export default ble;